package his

import (
	"fmt"
	"github.com/shopspring/decimal"
	"iot-base/common/cache"
	"iot-base/common/config"
	"iot-base/common/dbconn"
	"iot-base/common/dbobj"
	"iot-base/common/logger"
	"iot-base/common/notify"
	"iot-base/common/params"
	"iot-base/common/property"
	rc "iot-base/common/redis"
	"strconv"
	"strings"
	"time"
)

var (
	//modelIds 物模型ID，需要保存数据的字段
	modelIds = map[string][]string{
		"m_nox":  {"no", "no2", "nox"},
		"m_co":   {"co"},
		"m_so2":  {"so2"},
		"m_o3":   {"o3"},
		"m_pm10": {"pm"},
		"m_pm25": {"pm"},
	}
	pType       = "edge"
	thingStatus = "status"
)

func init() {
	if !dbconn.DBConnection.Migrator().HasTable(&dbobj.HisData{}) {
		dbconn.DBConnection.AutoMigrate(&dbobj.HisData{})
		if config.DBOption.Type == "postgres" && config.PType == "cloud" {
			// 初始化表
			dbconn.DBConnection.Exec(`CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;`)
			dbconn.DBConnection.Exec(`SELECT create_hypertable('his_data', 'pick_time',chunk_time_interval => interval '7 day');`)
		} else {
			property.HisDataSplit(time.Now())
		}
	}
}

func SetHisModelIds(data map[string][]string) {
	modelIds = data
}

//设置类型，判断是设备端还是云端

func SetHisPtype(params string) {
	pType = params
}

//SubHistoryData 订阅历史数据
func SubHistoryData() {
	notify.SubDataReady(func(pValue *params.ValueInRedis) {
		modelId, err := cache.GetModelId(pValue.TID)
		if err != nil || modelId == "" {
			return
		}
		if value, ok := modelIds[modelId]; ok {
			for _, v := range value {
				if v == pValue.PID {
					handleData(modelId, *pValue)
					return
				}
			}
		}
	})
}

//测试{23.5 23.5 23 23.5 float 1634537569 false no2 0  cdb3322d-3ffb-420d-90b4-7c80c5f35775 氮氧化物NOX#1 NO2  1634537569  单采}SID
func handleData(modelId string, pValue params.ValueInRedis) {
	//fmt.Println("test", pValue)
	pickTime := time.Unix(pValue.PickTime, 0)
	// 1. 计算分钟，小时平均值
	hour := strconv.Itoa(pickTime.Hour())
	minute := strconv.Itoa(pickTime.Minute())
	// 分钟数据
	unit := pValue.UnitName
	hourKey := fmt.Sprintf("his#hour#%v#%v#%v#%v", hour, modelId, pValue.TID, pValue.PID)
	minute5Key := fmt.Sprintf("his#5minute#%v_%v#%v#%v#%v", hour, pickTime.Minute()/5, modelId, pValue.TID, pValue.PID)
	if pValue.PID == thingStatus {
		//if pType == "edge" {
		minuteKey := fmt.Sprintf("his#minute#%v#%v#%v#%v", minute, modelId, pValue.TID, pValue.PID)
		setStatus(minuteKey, int(pValue.IValue), unit)
		//}
		setStatus(hourKey, int(pValue.IValue), unit)
		setStatus(minute5Key, int(pValue.IValue), unit)
	} else {
		//设备端才存储分钟数据
		//if pType == "edge" {
		minuteKey := fmt.Sprintf("his#minute#%v#%v#%v#%v", minute, modelId, pValue.TID, pValue.PID)
		statistics(minuteKey, pValue, pickTime, pickTime.Format("2006-01-02 15:04")+":00", unit)
		//}
		// 小时数据
		statistics(hourKey, pValue, pickTime, pickTime.Format("2006-01-02 15")+":00:00", unit)
		//每5分钟的数据
		minuteStr := strconv.Itoa(int(pickTime.Minute()/5) * 5)
		if len(minuteStr) == 1 {
			minuteStr = "0" + minuteStr
		}
		//minuteStr := strconv.Itoa(int(pickTime.Minute()/5) * 5)
		statistics(minute5Key, pValue, pickTime, pickTime.Format("2006-01-02 15")+":"+minuteStr+":00", unit)
	}

}

func setStatus(key string, status int, unit string) {
	result := rc.HGet(key, thingStatus)
	val, _ := result.Result()
	if val == "" {
		rc.HSet(key, thingStatus, status)
		rc.HSet(key, "unit", unit)
		rc.Expire(key, time.Minute*90)
	} else {
		value, _ := strconv.Atoi(val)
		if status > value {
			rc.HSet(key, thingStatus, status)
		}
	}
}

func statistics(key string, pValue params.ValueInRedis, pickTime time.Time, saveTime string, unit string) {
	rc.HSet(key, "tid", pValue.TID)
	val, _ := rc.HIncrBy(key, "count", 1).Result()
	if val == 1 {
		rc.HSet(key, "time", saveTime)
		rc.HSet(key, "unit", unit)
		rc.Expire(key, time.Minute*90)
		// 计算平均值
		//computeMinuteAve(pValue.ModelID, pickTime)
	}
	rc.HIncrByFloat(key, "val", pValue.FValue)
}

// computeAve 计算平均值
func computeAve(matchKey string) {
	var cursor uint64
	for {
		var keys []string
		var err error
		keys, cursor, err = rc.Scan(cursor, matchKey, 100).Result()
		if err != nil {
			fmt.Println("scan keys failed err:", err)
			return
		}
		for _, key := range keys {
			// 保存数据
			result := &params.RedisHistoryData{}
			isBool, _, _ := rc.GetHashObj(key, result)
			if isBool {
				result.Ave = result.Val / float64(result.Count)
				//todo 这里后期可以优化一下，批量插入数据库
				saveData(key, *result)
			}
		}
		if cursor == 0 {
			break
		}
	}
}
func saveData(key string, data params.RedisHistoryData) {
	//his#minute#53#m_pm10#d74fe490-8f08-422c-9ef1-352fb7bd09a9#status
	//his#minute#53#m_pm25#5a14e765-f9cc-4cd1-b2d3-16618814798e#pm
	if strings.Contains(key, thingStatus) {
		return
	}
	str := strings.Split(key, "#")
	if len(str) < 6 {
		return
	}

	class := str[1]
	modelId := str[3]
	field := str[5]

	thingStation := &dbobj.ThingStation{}
	_ = dbconn.DBConnection.Where("thing_id = ?", data.Tid).Where("model_id = ?", modelId).First(&thingStation).Error

	//获取状态
	str[5] = thingStatus
	statusKey := strings.Join(str, "#")
	statusStr, _ := rc.HGet(statusKey, thingStatus).Result()
	status, _ := strconv.Atoi(statusStr)

	rc.Del(key)
	// 延迟删除，给同一个设备的其他字段使用
	rc.Expire(statusKey, time.Minute)
	ave, _ := decimal.NewFromFloat(data.Ave).Round(2).Float64() // 保留两位小数
	pickTime, err := time.ParseInLocation("2006-01-02 15:04:05", data.Time, time.Local)
	if err != nil {
		fmt.Println("data.Time", err, data.Time, key)
		logger.SLog.Error("data.Time", err, data.Time, key)
	}

	saveData := &dbobj.HisData{
		StationID: thingStation.StationID,
		ThingID:   data.Tid,
		ModelID:   modelId,
		Classify:  class,
		Status:    status,
		Field:     field,
		Value:     ave,
		PickTime:  pickTime,
		Unit:      data.Unit,
		Total:     data.Count,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	err = dbobj.HisDataNew(dbconn.DBConnection).Add(saveData)
	if err != nil {
		logger.SLog.Error("add his_data", err)
	}
}

//定时任务保存数据，避免遗漏.

func CronHourSaveDb() {
	//computeHourAve(time.Now())
	hour := strconv.Itoa(time.Now().Add(-time.Hour).Hour()) // 1个小时之前的
	hourKey := fmt.Sprintf("his#hour#%v#*", hour)
	computeAve(hourKey)
}

//定时任务保存数据，避免遗漏.

func CronMinuteSaveDb() {
	//computeMinuteAve(time.Now())
	minute := strconv.Itoa(time.Now().Add(-time.Minute * 2).Minute()) // 1分钟之前
	minuteKey := fmt.Sprintf("his#minute#%v#*", minute)
	computeAve(minuteKey)
}

func Cron5MinuteSaveDb() {
	datatime := time.Now().Add(-time.Minute * 5)
	minute5Key := fmt.Sprintf("his#5minute#%v_%v#*", datatime.Hour(), datatime.Minute()/5)
	computeAve(minute5Key)
}

func Test() {
	fmt.Println(rc.Exists("his_hour#15#m_o3#o3#1").Result())
}
